/**------------------------------------------------------------------------
    File        : StandardBindingResolver
    Purpose     : Returns all bindings for (typically) a service that meet
                  service, name and condition requirements. More than one
                  binding may be returned. The decision of which binding to
                  use is done by the InjectABL Kernel.
    Syntax      : 
    Description : 
    @author pjudge
    Created     : Tue Mar 09 09:02:30 EST 2010
    Notes       : 
  ---------------------------------------------------------------------- */
routine-level on error undo, throw.

using OpenEdge.Core.InjectABL.Lifecycle.StandardScopeEnum.
using OpenEdge.Core.InjectABL.Binding.IBindingResolver.
using OpenEdge.Core.InjectABL.Binding.IBinding.
using OpenEdge.Core.InjectABL.Binding.Conditions.ICondition.
using OpenEdge.Core.InjectABL.Binding.Conditions.Condition.
using OpenEdge.Core.System.NotFoundError.

using OpenEdge.Lang.Collections.IMap.
using OpenEdge.Lang.Collections.IList.
using OpenEdge.Lang.Collections.List.
using OpenEdge.Lang.Collections.IIterator.
using OpenEdge.Lang.Collections.ObjectStack.
using Progress.Lang.Class.
using Progress.Lang.Object.

class OpenEdge.Core.InjectABL.Binding.StandardBindingResolver implements IBindingResolver:
    
    /** Returns valid binding(s) for the service from a set of bindings
        
        @param IMap The complete set of bindings to resolve
        @param Class The service type
        @param character The binding name
        @return IList  The list of bindings corresponding to the service & name */
    method public IList Resolve(input poBindings as IMap,
                                input poService as class Class,
                                input pcName as character):
        define variable oResolvedBindings as IList no-undo.
        define variable oServiceBindings as IList no-undo.
        define variable oIterator as IIterator no-undo.
        define variable oBinding as IBinding no-undo.
        
        oServiceBindings = cast(poBindings:Get(poService), IList).
        if not valid-object(oServiceBindings) then
            undo, throw new NotFoundError(
                    substitute('Service &1', poService:TypeName),
                    'kernel bindings').

        assign oResolvedBindings = new List().
               oIterator = oServiceBindings:Iterator().
        
        do while oIterator:HasNext():
            oBinding = cast(oIterator:Next(), IBinding).
            
            /* Names need to match */
            if oBinding:Name ne pcName then
                next.
            
            /* All conditions need to be satisfied */
            if oBinding:IsConditional and
               not ResolveCondition(oBinding) then
                next.
            
            /* We can use this binding */
            oResolvedBindings:Add(oBinding).
        end.
        
        return oResolvedBindings.
    end method.
    
    /** Resolved the conditions attached to a binding
        
        @param IBinding The biding in question
        @return Logical Whether the binding meets the conditions or not. */
    method protected logical ResolveCondition(input poBinding as IBinding):
        define variable oFIFOConditions as ObjectStack no-undo.
        define variable oCondition as ICondition no-undo.
        define variable lConditionsMet as logical no-undo.
        define variable lSubconditionMet as logical no-undo.
        define variable oLHS as ICondition no-undo.
        define variable oClauseOperator as ICondition no-undo.
        define variable oRHS as ICondition no-undo.
        define variable oOperator as ICondition no-undo.
        
        /* Extract the stack since we basically want to stack to be immutable, since we may
           use the binding again. Pop() will clear the stack. */
        oFIFOConditions = cast(poBinding:Condition:Clone(), ObjectStack).
        /* Invert the stack since we want to work backwards through the stack - FIFO rather than 
           LIFO - since the conditions are added 'forwards' and we want to read 'em that way. */
        oFIFOConditions:Invert().

        lConditionsMet = true.
        
        do while oFIFOConditions:Size gt 0:
            assign oCondition = cast(oFIFOConditions:Pop(), ICondition).
            
            case oCondition:
                /* 'When' starts a new set of evaluations. We treat this as an And (true);
                   this is necessary for cases where there is only one condition. */ 
                when Condition:When then
                    assign oOperator = Condition:And
                           lSubconditionMet = true.
                
                when Condition:And or 
                when Condition:Or then
                    oOperator = oCondition.
                
                otherwise
                    /* Clauses appear on the stack as Condition, Is/Not, Condition */
                    assign oLHS = oCondition
                           oClauseOperator = cast(oFIFOConditions:Pop(), ICondition) 
                           oRHS = cast(oFIFOConditions:Pop(), ICondition)
                           /* Use RHS for resolver, since the RHS is more likely to be more specialised:
                              the LHS might be 'Name' and the RHS refer to a session type or ui type.
                              
                              The resolver can check the LHS if it chooses to, for type compatability */            
                           lSubconditionMet = oRHS:GetResolver():EvaluateClause(oLHS, oClauseOperator, oRHS).
            end case.
            
            /* if need be, do the comparison */
            if valid-object(oOperator) then
            do:
                case oOperator:
                    when Condition:Or then
                    do:
                        lConditionsMet = lConditionsMet or lSubconditionMet.
                        
                        /* If we've satisfied at least one of the OR conditions,
                           we can leave this When block. Don't leave is the conditions
                           are false, since we may come across a true condition later. */
                        if lConditionsMet then
                            leave.
                    end.
                    
                    when Condition:And then
                    do:
                        lConditionsMet = lConditionsMet and lSubconditionMet.
                        
                        /* If we come across a single false condition, leave, since we know there
                           is nothing more to do.  */
                        if not lConditionsMet then
                            leave.
                    end.
                end case.   /* operator */
            end.    /* valid operator */
        end.   /* stuff on stack */
        
        return lConditionsMet.
    end method.
    
end class.